#include "arena.h"
#include "assert.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>


union AlignTag {
	int i;
	long l;
	void *p;
	long *lp;
	double d;
	long double ld;
	void (*fn)();
};

#define ALIGN_ROUND(nbytes) ((nbytes+sizeof(union AlignTag)-1) / sizeof(union AlignTag) * sizeof(union AlignTag))
#define T arena_T

struct T {
	T prev;
	char *avail;
	char *limit; // 指向最后有效字节的下一字节
};

struct Header {
	struct T arena;
	union AlignTag align;
};

static T _freelist = NULL;
static int _freecount = 0;

const Except_T ex_arena_new_failed = {"arena new failed"};
const Except_T ex_arena_alloc_failed = {"arena alloc failed"};

T
arena_new() {
	T arena = malloc(sizeof(*arena));
	if (arena == NULL) {
		RAISE(ex_arena_new_failed);	
	}
	arena->prev = NULL;
	arena->avail = arena->limit = NULL;
	return arena;
}

void
arena_dispose(T *ap) {
	assert(ap != NULL && *ap != NULL);
	arena_free(*ap);
	free(*ap);
	*ap = NULL;
}

void*
arena_alloc(T arena, long nbytes, const char *file, int line) {
	assert(arena);
	assert(nbytes > 0);

	long need;
	T ptr;
	char *limit;
	nbytes = ALIGN_ROUND(nbytes);
	while (arena->limit - arena->avail < nbytes) {
		if ((ptr = _freelist) != NULL) {
			limit = _freelist->limit;
			_freelist = _freelist->prev;
			_freecount--;
		} else {
			need = ALIGN_ROUND(nbytes + sizeof(struct Header) + 10*1024); // 预先分配 10k 字节
			ptr = malloc(need);
			if (ptr == NULL) {
				if (file)
					except_raise(&ex_arena_alloc_failed, file, line);
				else
					RAISE(ex_arena_alloc_failed);
			}
			limit = (char*)ptr + need;
		}

		*ptr = *arena;
		arena->prev = ptr;
		arena->avail = (char*)ptr + sizeof(struct Header); // 确保被分配的空间地址对齐
		arena->limit = limit;
	}

	void *ret = arena->avail;
	arena->avail = (char*)(arena->avail) + nbytes;
	return ret;
}

void*
arena_calloc(T arena, long count, long nbytes, const char *file, int line) {
	assert(arena != NULL);
	assert(count > 0);
	void* ptr = arena_alloc(arena, count * nbytes, file, line);
	memset(ptr, 0, count * nbytes);
	return ptr;
}

void
arena_free(T arena) {
	assert(arena != NULL);
	while (arena->prev) {
		struct T tmp = *(arena->prev);

		const int cache = 10; // 只保存固定数量，防止占用太多内存
		if (_freecount < cache) {
			arena->prev->prev = _freelist;
			_freelist = arena->prev;
			_freelist->limit = arena->limit;
			_freecount++;
		} else {
			free(arena->prev);
		}

		*arena = tmp;
	}
	assert(arena->avail == NULL);
	assert(arena->limit == NULL);
}

void
arena_dump(T arena) {
	assert(arena != NULL);

	printf("   arena dump\n");
	long use_bytes = 0;
	T block = arena;
	while (block->prev) {
		use_bytes += (long)(block->limit - (char*)block->prev);
		// printf(" block info size:%ld avail:%ld, ptr:%p\n", 
		// 	(long)(block->limit - (char*)block->prev), (long)(block->limit - ((char*)block->prev+sizeof(struct Header))), block->prev);
		block = block->prev;
	}

	printf("   free list dump\n");
	block = _freelist;
	long free_bytes = 0;
	while (block) {
		free_bytes += (long)(block->limit - (char*)block);
		// printf(" block info size:%ld avail:%ld, ptr:%p\n", 
		// 	(long)(block->limit - (char*)block), (long)(block->limit - ((char*)block+sizeof(struct Header))), block->prev);
		block = block->prev;	
	}
	printf(" use_bytes:%.4f KiB free_bytes:%.4f KiB\n arena total bytes:%.4f KiB\n", 
		(double)use_bytes/1024, (double)free_bytes/1024, (double)(use_bytes+free_bytes)/1024);
}
